#include <stdint.h>
#include <string.h>
#include <stddef.h>
#include "image_mgr.h"
#include "bs_app.h"
#include "inner_flash.h"

#define LOG_TAG    "[img]"
#include "log.h"

#define REGION_BUF_SIZE                   (offsetof(bs_images_mgr_t, dummy) - offsetof(bs_images_mgr_t, len))
#define REGION_SECTOR_SIZE                (16*1024)

#define IMF_INVALID_IMG                   0x0002
#define IMF_EMPTY                         0x0004
#define IMF_ERROR                         0x0010
#define IMF_LOG_FAILED                    0x0020
#define IMF_ERR_INCONSISTENT              (0x0040|IMF_ERROR)

#define JTYPE_SWITCH_IMG                  1
#define JTYPE_DELETE_IMG                  2


bs_images_mgr_t         hImage;
bs_images_mgr_t         *imgr = &hImage;

wear_level_flash_t      img_burn_info;
static journal_t        journal;



/*!
    \brief 填充时间结构体
    \param[in]  none
    \param[out] none
    \retval 0: successful，1：failed
*/
void bs_imgr_gettime(bs_timespec_t *ctim)
{
    uint8_t buf[32];
    rtc_acquire_time(buf);
    ctim->tm_year = buf[0];
    ctim->tm_mon = buf[1];
    ctim->tm_day = buf[2];
    ctim->tm_hour = buf[3];
    ctim->tm_min = buf[4];
    ctim->tm_sec = buf[5];
    ctim->tm_msec = 0;
    return;
}


/*!
    \brief 擦除镜像同步区域
    \param[in]
    \param[out]
    \retval 0: successful，1：failed
*/
int bs_imgr_region_clear()
{
    inn_flash_eraser_page(INNR_IMAGE_REGION_STADDR);
    inn_flash_eraser_page(INNR_IMAGE_BAKEUP_STADDR);
    inn_flash_eraser_page(INNR_IMAGE_JOURNAL_STADDR);
    imgr->flag = IMF_EMPTY|IMF_INVALID_IMG;
    imgr->images_num = 0;
    memset((uint8_t *)&imgr->len, 0, REGION_BUF_SIZE);
    imgr->i_active = 0xFF;
    return 0;
}


/*!
    \brief 读取内部flash数据
    \param[in]  none
    \param[out] none
    \retval 0
*/
static int bs_imgr_region_read(uint32_t addr)
{
    inn_flash_read_8b(addr, REGION_BUF_SIZE, (uint8_t *)&imgr->len);
    return 0;
}


/*!
    \brief 
    \param[in]  addr: 
    \param[out] none
    \retval 0：find successful，1：写入失败
*/
static int bs_imgr_region_write()
{
    uint8_t tmp_cmp_buf[4096];
    int i;
    uint8_t *p1,*p2;
    uint32_t bkp_sect = imgr->bkp_addr;


    p1 = tmp_cmp_buf;
    p2 = (uint8_t *)&imgr->len;

    if (imgr->load_addr != INNR_IMAGE_REGION_STADDR || imgr->bkp_addr != INNR_IMAGE_BAKEUP_STADDR) {
        imgr->flag |= IMF_ERROR;
        return 1;
    }
    inn_flash_eraser_page(imgr->load_addr);
    inn_flash_write_8b(imgr->load_addr, REGION_BUF_SIZE, (uint8_t *)&imgr->len);

    inn_flash_read_8b(imgr->load_addr, REGION_BUF_SIZE, tmp_cmp_buf);
    for (i=0; i<imgr->len; i++) {
        if (*p1++ != *p2++ ) {
            imgr->flag |= IMF_ERROR;
            LOGE("write image node region error");
            return 1;
        }
    }
    inn_flash_eraser_page(bkp_sect);
    inn_flash_write_8b(bkp_sect, REGION_BUF_SIZE, (uint8_t *)&imgr->len);
    return 0;
}


/*!
    \brief 开始日志: 找到空闲项并写入开始标记TxB
    \param[in]  none
    \param[out] none
    \retval 0: successful，1：failed
*/
static int bs_imgr_journal_start()
{
    return 0;
}


/*!
    \brief 写入具体事务内容
    \param[in]  none
    \param[out] none
    \retval 0: successful，1：failed
*/
static int bs_imgr_journal_write()
{
    return 0;
}


/*!
    \brief 释放日志
    \param[in]  none
    \param[out] none
    \retval 0: successful，1：failed
*/
static int bs_imgr_journal_release(uint32_t addr)
{
    uint32_t expire_item = 0;
    int res = 0;


    if (addr-journal.log_addr > journal.log_page_size) {
        LOGE("journal address out of cross");
        return 1;
    }

    res = inn_flash_write_8b(addr, 4, (uint8_t *)&expire_item);
    if (res != 0) {
        LOGE("eraser journal item failed");
    }
    
    return res;
}


/*!
    \brief 提交日志: 计算校验和，写入结束标记TxE
    \param[in]  none
    \param[out] none
    \retval 0: successful，-1：failed, 1:参数错误
*/
static int bs_imgr_journal_stop()
{
    uint8_t temp_buf[32];
    uint32_t i, len, addr, retry_times;
    uint8_t *p;
    uint32_t expire_item = 0;


    len = journal.b_len;
    addr = journal.log_ofs;
    retry_times=0;

    if (journal.log_addr != INNR_IMAGE_JOURNAL_STADDR || len > 24) return 1;

    journal.magic = 0x5A5A;
    len += 4;
    memcpy(temp_buf, &journal.magic, len);
    journal.check_sum = crc32_continue(0, temp_buf, len);

    *(uint32_t *)(&temp_buf[len]) = journal.check_sum;
    len += 4;

repeat_try:
    if(retry_times++ > 10)return -1;
    
    if (addr - journal.log_addr >= journal.log_page_size) {
        inn_flash_eraser_page(INNR_IMAGE_JOURNAL_STADDR);
        addr = journal.log_addr;
    }

    inn_flash_write_8b(addr, len, temp_buf);
    for (i=0, p=(uint8_t *)addr; i<len; i++, p++) {
        if (*p != temp_buf[i]) {
            inn_flash_write_8b(addr, 2, (uint8_t *)&expire_item);
            addr += 32;
            LOGI("retry writing image journal item ...");

            goto repeat_try;
        }
    }

    journal.log_ofs = addr;
    return 0;
}


/*!
    \brief 修复镜像区域
    \param[in]  none
    \param[out] none
    \retval 0: successful，1：failed
*/
static int bs_imgr_region_fixup()
{
    uint8_t index;
    int res;
    uint32_t crc_val, len;
    bs_image_node_t *cur_img;


    if (journal.magic != 0x5A5A || journal.b_len>24) return 0;

    len = journal.b_len+4;
    crc_val = crc32_continue(0, &journal.magic, len);
    journal.check_sum = *(uint32_t *)((uint8_t *)&journal.magic + len);

    if (crc_val != journal.check_sum) return 0;
    LOGI("flash image region consistency check...");
    switch (journal.type) {
        case JTYPE_SWITCH_IMG:
            index = journal.b_data[0];
            if (index == 0xFF) { /* 上一个镜像的烧录次数信息无效 */
            } else {
                cur_img = &imgr->images[index];
                cur_img->used_count     = *(uint32_t *)(&journal.b_data[2]);
                cur_img->inv_used_count = ~cur_img->used_count;
                cur_img->ok_count       = *(uint32_t *)(&journal.b_data[6]);
                cur_img->inv_ok_count   = ~cur_img->ok_count;
            }
            index = journal.b_data[1];

            /* replay */
            cur_img = &imgr->images[index];
            memcpy(imgr->activated_image, cur_img->file_name, 128+1);
            imgr->i_active = index;
            imgr->crc32 = crc32_continue(0, imgr->raw_data+8, imgr->len-8);

            bs_imgr_region_write();

            flash_level_reset(&img_burn_info);

            img_burn_info.u.data.used_count = cur_img->used_count;
            img_burn_info.u.data.ok_count = cur_img->ok_count;
            res = flash_level_write_entry(&img_burn_info);
            if (res != 0) {
                LOGE("write burn info to internal flash failed");
            } else {
                LOGI("image switch to \"%s\"", cur_img->file_name);
            }
        break;

        default:
        break;
    }
    
    return res;
}


/*!
    \brief 增/删 文件时，需要更新
    \param[in]  none
    \param[out] none
    \retval 0: successful，1：failed
*/
static void bs_imgr_update_iactive()
{
    imgr->i_active = 0xFF;
    for (int i=0; i<imgr->images_num; i++) {
        if (strcmp(imgr->images[i].file_name, imgr->activated_image) == 0) {
            imgr->i_active = i;
            break;
        }
    }
    if (imgr->i_active == 0xFF) imgr->flag |= IMF_INVALID_IMG;
}


/*!
    \brief 根据片内flash的 根目录修复spi flash
    \param[in] 
    \param[out] none
    \retval 0: successful，1：failed
*/
int bs_imgr_resume_rootdir()
{
    // 如何保证两个目录一致，内部flash的目录要最后更新 或者说 以文件数量最少的版本为主
    // 如果不是这样子，则严重错误：无法修复，需要手动清除所有镜像
    // uint8_t temp_buf[32*16];
    // 打开根目录

    // 写入根目录
    // 关闭根目录
    return 0;
}


/*!
    \brief 填充镜像节点信息
    \param[in]  none: 
    \param[out] none
    \retval 0: successful，1：failed
*/
int bs_imgr_init()
{
    uint32_t  crc_val;
    uint32_t  i;
    uint8_t   *pData;
    wear_level_flash_t *wl = &img_burn_info;

    /* initial constant variables */
    wl->page_size = UPDATE_REGION_PAGE_SIZE;
    wl->entry_size = UPDATE_REGION_ENTRY_SIZE;
    wl->st_page = INNR_UPDATE_REGION_STADDR;
    wl->page_nr = 2;
    wl->end_page = wl->st_page + (wl->page_nr-1)*wl->page_size;
    wl->last_entry = __ROUNDDOWN(wl->page_size-1, wl->entry_size);
    
    /* initial image manage handle */
    imgr->load_addr = INNR_IMAGE_REGION_STADDR;
    imgr->bkp_addr  = INNR_IMAGE_BAKEUP_STADDR;
    imgr->flag = 0;
    imgr->raw_data = (uint8_t *)&imgr->len;
    bs_imgr_region_read(imgr->load_addr);
    if (imgr->images_num > MAX_IMAGE_FILES_NR) goto check_error;
    if (imgr->len > REGION_BUF_SIZE || imgr->len < 8) goto check_error;
    crc_val = crc32_continue(0, imgr->raw_data+8, imgr->len-8);
    if (crc_val != imgr->crc32) {
        LOGE("image region_0 check crc no passed");
        goto check_error;
    }
    goto success;

check_error:
    pData = imgr->raw_data;
    // 第一个区域无效，查看是否为空
    for(i=0; i<REGION_BUF_SIZE && *pData++ == 0xFF; i++) {
    }
    if (i == REGION_BUF_SIZE) imgr->flag |= IMF_EMPTY;

    // 查看备份区域
    bs_imgr_region_read(imgr->bkp_addr);
    if (imgr->images_num > MAX_IMAGE_FILES_NR) goto check_error2;
    if (imgr->len > REGION_BUF_SIZE || imgr->len < 8) goto check_error2;
    crc_val = crc32_continue(0, imgr->raw_data+8, imgr->len-8);
    if (crc_val != imgr->crc32) {
        LOGE("image region_1 check crc no passed");
        goto check_error2;
    }

    // 备份区域有效
    goto success;

check_error2:
    // 查看是否为空
    pData = imgr->raw_data;
    // 第一个区域无效，查看是否为空
    for(i=0; i<REGION_BUF_SIZE && *pData++ == 0xFF; i++) {
    }
    if (i == REGION_BUF_SIZE && (imgr->flag & IMF_EMPTY)) {
        imgr->len = 0;
        imgr->images_num = 0;
        return 0;
    }
    imgr->flag |= IMF_ERROR;
    imgr->len = 0;
    imgr->images_num = 0;
    return 1;

success:

#ifdef BS_INNR_JOURNAL
    journal.log_addr = INNR_IMAGE_JOURNAL_STADDR;
    journal.log_page_size = INNER_IMAGE_JOURNAL_SIZE;
    uint16_t *p = (uint16_t *)journal.log_addr;

    for(i=0; i<journal.log_page_size; i+=32, p+=16) {
        journal.log_ofs = (uint32_t)p;
        if (*p == 0xFFFF) break;
        if (*p == 0x5A5A) {
            memcpy(&journal.magic, p, 32);
            bs_imgr_region_fixup();
            bs_imgr_journal_release(journal.log_ofs);
        }
    }
#endif

    // 如果删除的过程中突然断电，根目录可能不一致
    bs_imgr_list_image();
    // 检查当前激活的镜像是否有效
    bs_imgr_update_iactive();
    
    if (imgr->i_active != 0xFF) {
        // 文件名有效，检查烧录次数是否有效？如果无效，需要重新选择
        if (flash_level_find_entry(&img_burn_info) != 0) {
            LOGE("[E]current image info invaild, Please select an image again!");
            imgr->i_active = 0xFF;
            imgr->flag |= IMF_INVALID_IMG;
        } else {
            LOGI("current activated image: %s", imgr->activated_image);
        }
    }
    return 0;
}


/*!
    \brief 删除一个镜像
    \param[in] 
    \param[out] none
    \retval 0: successful，1：failed
*/
int bs_imgr_del_image(uint8_t index)
{
    uint16_t i_max;

    if (imgr->flag & (IMF_ERROR|IMF_EMPTY)) return 1;
    if (imgr->images_num == 0) {
        imgr->flag |= IMF_EMPTY;
        return 1;
    }
    i_max = imgr->images_num - 1;
    if (index > i_max) return 1;
    
    // 如果是最后一个镜像 或者 只有一个镜像，则直接删除；
    // 如果超过一个镜像，且移除的不是最后一个镜像，需要移动目录项：即后面的要移动到前面

    if (i_max >=1 && index != i_max) {
        memmove(&imgr->images[index], &imgr->images[index+1], sizeof(bs_image_node_t)*(i_max-index));
    }
    memset(&imgr->images[i_max].file_name, 0xFF, 32);

    if (--imgr->images_num == 0) {
        bs_imgr_region_clear();
        return 0;
    } else {
        imgr->len -= sizeof(bs_image_node_t);
    }

    if (index == imgr->i_active) {
        memset(imgr->activated_image, 0, 128+1);
    }
    imgr->crc32 = crc32_continue(0, imgr->raw_data+8, imgr->len-8);
    // checkpoint
    bs_imgr_region_write();
    
    if (f_unlink_by_index(index, i_max) != FR_OK) {
        LOGE("remove external flash directory entry error");
    }

    bs_imgr_update_iactive();
    return 0;
}



/*!
    \brief 添加镜像，名字不能重复
    \param[in] 
    \param[out] none
    \retval 0: successful，1：failed
*/
int bs_imgr_add_image(bs_image_node_t *new_img, FIL *fp)
{
    int      i, res = 0;
    uint8_t  i_max;

    if (imgr->flag & IMF_ERROR) return 1;

    for (i=0; i<imgr->images_num; i++) {
        if (strcmp(imgr->images[i].file_name, new_img->file_name) == 0) {
            // TODO: 覆盖比较繁琐，需要从数组中摘下，再添加到首部
            LOGI("current file already exists!");
            bs_imgr_del_image(i);
        }
    }

    new_img->uid = (uint16_t)(bs_jiffies & 0xFFFF);
    bs_imgr_gettime(&new_img->download_time);

    if (imgr->flag & IMF_EMPTY) {
        imgr->images_num = 0;
    }

    /* insert to the tail of image node list */
    i_max = imgr->images_num;
    memcpy(&imgr->images[i_max], new_img, sizeof(bs_image_node_t));

    /* update extern flash root directiry */
    if (f_create_by_index(fp, i_max, new_img->file_name, new_img->uid) != FR_OK) res = 1;

    return res;
}



/*!
    \brief 添加镜像结束. 原则：先添加外部flash数据结构，再更新内部
    \param[in] 
    \param[out] none
    \retval 0: successful，1：failed
*/
int bs_imgr_save_image(FIL *fp)
{
    if (f_close(fp) != FR_OK) return 1;

    imgr->images_num++;
    imgr->len = (4+4+2) + sizeof(bs_image_node_t)*imgr->images_num;
    imgr->crc32 = crc32_continue(0, imgr->raw_data+8, imgr->len-8);

    bs_imgr_region_write();
    if (imgr->flag & IMF_EMPTY) {
        imgr->flag &= ~IMF_EMPTY;
    }
    bs_imgr_update_iactive();
    return 0;
}


/*!
    \brief 列举出当前所有镜像
    \param[in] 
    \param[out] none
    \retval 0: successful，1：failed
*/
int bs_imgr_list_image()
{
    uint8_t temp_buf[32*16];
    int i, j;
    bs_image_node_t *cur_img;

    uint8_t entry, *p2;
    uint32_t mismatch = 0;

    if (imgr->flag & (IMF_ERROR|IMF_EMPTY)) return 1;
    if (imgr->images_num > 0) {
        // 一致性检查: 如果 inner==[A C D] --> external==[A B C D]，则需删除 external[B]
        LOGI("total images: %d\r\n", imgr->images_num);
        spi_flash_buffer_read_block(temp_buf, 1*4096, sizeof(temp_buf));

        p2 = (uint8_t *)temp_buf;
			  bs_writer_print_hexdump(p2, 32*16);
        for (i=0, j=0; i<MAX_IMAGE_FILES_NR && j<imgr->images_num; i++, p2+=32) {
            cur_img = &imgr->images[j];
            LOGI("%02d: %s. uid=<0x%04hx, 0x%04hx>, count: %d", j, cur_img->file_name, cur_img->uid, *(uint16_t *)(p2+DIR_UID), cur_img->used_count);
            if (cur_img->uid != *(uint16_t*)(p2+DIR_UID)) {
                mismatch++;
                entry = i;
                LOGE("current image info is inconsistent");
            } else {
                j++;
            }
        }
        if (mismatch == 0) {
            LOGI("The directory consistency check is passed.");
            goto check_exit;
        }
        if (mismatch > 1) {
            // 没救了
            imgr->flag |= IMF_ERR_INCONSISTENT;
            LOGE("An unrepairable error was encountered!");
            goto check_exit;
        }

        // 尝试删除多余的目录项，并断开fat链表
        LOGI("Try repairing the root directory...");
        if (f_unlink_by_index(entry, imgr->images_num+1) != FR_OK) {
            imgr->flag |= IMF_ERR_INCONSISTENT;
            LOGE("Repair failed");
        } else {
            LOGE("Repair ok");
        }
    }

check_exit:
    return 0;
}



/*!
    \brief 激活某一个镜像
    \param[in] 
    \param[out] none
    \retval 0: successful，1：failed
*/
int bs_imgr_select_image(uint8_t index)
{
    int i, res=0;
    bs_image_node_t *cur_img;


    if (imgr->flag & (IMF_ERROR|IMF_EMPTY)) return 1;
    if (index+1 > imgr->images_num) return 1;

    i = imgr->i_active;
    if (i == index) return 0;

    // 首先判断当前镜像是否有效
    if (i < imgr->images_num && strcmp(imgr->images[i].file_name, imgr->activated_image) == 0) {
        cur_img = &imgr->images[i];
        cur_img->used_count     = img_burn_info.u.data.used_count;
        cur_img->inv_used_count = ~img_burn_info.u.data.used_count;
        cur_img->ok_count       = img_burn_info.u.data.ok_count;
        cur_img->inv_ok_count   = ~img_burn_info.u.data.ok_count;
    } else
        i = 0xFF;

#ifdef BS_INNR_JOURNAL
    // 日志内容：TxB | 上一个镜像 | 现在的镜像 | 上一个镜像的烧录次数 | TxE
    bs_imgr_journal_start();

    journal.type = JTYPE_SWITCH_IMG;
    journal.b_len = 10;
    
    journal.b_data[0] = i;
    journal.b_data[1] = index;

    *(uint32_t *)(&journal.b_data[2]) = img_burn_info.u.data.used_count;
    *(uint32_t *)(&journal.b_data[6]) = img_burn_info.u.data.ok_count;

    bs_imgr_journal_write();
    if (bs_imgr_journal_stop() != 0) {
        imgr->flag |= IMF_LOG_FAILED;
        LOGE("write image journal error");
    }
#endif

    /* checkpoint */
    cur_img = &imgr->images[index];
    memcpy(imgr->activated_image, cur_img->file_name, 128+1);
    imgr->i_active = index;
    imgr->crc32 = crc32_continue(0, imgr->raw_data+8, imgr->len-8);

    bs_imgr_region_write();

    flash_level_reset(&img_burn_info);

    img_burn_info.u.data.used_count = cur_img->used_count;
    img_burn_info.u.data.ok_count = cur_img->ok_count;
    res = flash_level_write_entry(&img_burn_info);

#ifdef BS_INNR_JOURNAL
    bs_imgr_journal_release(journal.log_ofs);
    journal.log_ofs += 32;
#endif

    if (res != 0) {
        LOGE("[e] write burn info to internal flash failed");
        imgr->flag |= IMF_ERROR;
        return res;
    }
    imgr->flag &= ~IMF_INVALID_IMG;
    LOGI("switch to \"%s\"", cur_img->file_name);
    return res;
}


/*!
    \brief 打开当前激活的镜像
    \param[in] none
    \param[out] none
    \retval 0: successful，1：failed
*/
int bs_imgr_open_image(FIL *fp)
{
    int i;
    // bs_image_node_t *cur_img;
    
    if (imgr->flag & (IMF_ERROR|IMF_EMPTY|IMF_INVALID_IMG)) return 1;
    // for (i=0; i<imgr->images_num; i++) {
    //     if (strcmp(imgr->images[i].file_name, fname) == 0) {
    //         break;
    //     }
    // }
    // if (i >= imgr->images_num) {
    //     LOGI("no found specific image file");
    //     return 1;
    // }

    i = imgr->i_active;
    if(i == 0xFF || i>imgr->images_num-1) {
        LOGE("no found specific image file");
        return 1;
    }

    // cur_img = &imgr->images[i];
    if (f_open_by_index(fp, i, FA_READ) != FR_OK) return 1;

    return 0;
}


/*!
    \brief 无论烧录成功或者失败都要调用
    \param[in] 
    \param[out] none
    \retval 0: successful，1：failed
*/
int bs_imgr_close_image(FIL *fp)
{
    // int i;
    // bs_image_node_t *cur_img;
    uint32_t used_times;
    uint32_t ok_times;
    

    if (f_close(fp) != FR_OK) return 1;

    // 更新烧录次数
    used_times = ++img_burn_info.u.data.used_count;
    ok_times = ++img_burn_info.u.data.ok_count;
    flash_level_write_entry(&img_burn_info);
    LOGI("used_count: 0x%x, ok_count: 0x%x", used_times, ok_times);

    return 0;
}
